Update PeakDetectorAgent options and change it so that it detects peaks on the rise instead of fall.

Andrew Cantino 10 years ago
parent
commit
7526f01ba4
4 changed files with 32 additions and 29 deletions
  1. 1 0
      CHANGES.md
  2. 1 1
      VERSION
  3. 24 21
      app/models/agents/peak_detector_agent.rb
  4. 6 7
      spec/models/agents/peak_detector_agent_spec.rb

+ 1 - 0
CHANGES.md

@@ -1,5 +1,6 @@
1 1
 # Changes
2 2
 
3
+* 0.2 (Nov 6, 2013)    - PeakDetectorAgent now uses `window_duration_in_days` and `min_peak_spacing_in_days`.  Additionally, peaks trigger when the time series rises over the standard deviation multiple, not after it starts to fall.
3 4
 * June 29, 2013        - Removed rails\_admin because it was causing deployment issues. Better to have people install their favorite admin tool if they want one.
4 5
 * June, 2013           - A number of new agents have been contributed, including interfaces to Weibo, Twitter, and Twilio, as well as Agents for translation, sentiment analysis, and for posting and receiving webhooks.
5 6
 * March 24, 2013 (0.1) - Refactored loading of Agents for `check` and `receive` to use ids instead of full objects.  This should fix the too-large delayed_job issues.  Added `system_timer` and `fastercsv` to the Gemfile for the Ruby 1.8 platform.

+ 1 - 1
VERSION

@@ -1 +1 @@
1
-0.1
1
+0.2

+ 24 - 21
app/models/agents/peak_detector_agent.rb

@@ -11,9 +11,9 @@ module Agents
11 11
 
12 12
       Set `expected_receive_period_in_days` to the maximum amount of time that you'd expect to pass between Events being received by this Agent.
13 13
 
14
-      You may set `window_duration` to change the default memory window length of two weeks,
15
-      `peak_spacing` to change the default minimum peak spacing of two days, and
16
-      `std_multiple` to change the default standard deviation threshold multiple of 3.
14
+      You may set `window_duration_in_days` to change the default memory window length of `14` days,
15
+      `min_peak_spacing_in_days` to change the default minimum peak spacing of `2` days (peaks closer together will be ignored), and
16
+      `std_multiple` to change the default standard deviation threshold multiple of `3`.
17 17
     MD
18 18
 
19 19
     event_description <<-MD
@@ -61,27 +61,22 @@ module Agents
61 61
       memory[:peaks][group] ||= []
62 62
 
63 63
       if memory[:data][group].length > 4 && (memory[:peaks][group].empty? || memory[:peaks][group].last < event.created_at.to_i - peak_spacing)
64
-        average_value, standard_deviation = stats_for(group, :skip_last => 2)
65
-        newest_value = memory[:data][group][-1].first.to_f
66
-        second_newest_value, second_newest_time = memory[:data][group][-2].map(&:to_f)
67
-
68
-        #pp({:newest_value => newest_value,
69
-        #    :second_newest_value => second_newest_value,
70
-        #    :average_value => average_value,
71
-        #    :standard_deviation => standard_deviation,
72
-        #    :threshold => average_value + std_multiple * standard_deviation })
73
-
74
-        if newest_value < second_newest_value && second_newest_value > average_value + std_multiple * standard_deviation
75
-          memory[:peaks][group] << second_newest_time
76
-          memory[:peaks][group].reject! { |p| p <= second_newest_time - window_duration }
77
-          create_event :payload => {:message => options[:message], :peak => second_newest_value, :peak_time => second_newest_time, :grouped_by => group.to_s}
64
+        average_value, standard_deviation = stats_for(group, :skip_last => 1)
65
+        newest_value, newest_time = memory[:data][group][-1].map(&:to_f)
66
+
67
+        #p [newest_value, average_value, average_value + std_multiple * standard_deviation, standard_deviation]
68
+
69
+        if newest_value > average_value + std_multiple * standard_deviation
70
+          memory[:peaks][group] << newest_time
71
+          memory[:peaks][group].reject! { |p| p <= newest_time - window_duration }
72
+          create_event :payload => {:message => options[:message], :peak => newest_value, :peak_time => newest_time, :grouped_by => group.to_s}
78 73
         end
79 74
       end
80 75
     end
81 76
 
82 77
     def stats_for(group, options = {})
83 78
       data = memory[:data][group].map { |d| d.first.to_f }
84
-      data = data[0...(memory[:data][group].length - (options[:skip_last] || 0))]
79
+      data = data[0...(data.length - (options[:skip_last] || 0))]
85 80
       length = data.length.to_f
86 81
       mean = 0
87 82
       mean_variance = 0
@@ -99,15 +94,23 @@ module Agents
99 94
     end
100 95
 
101 96
     def window_duration
102
-      (options[:window_duration].present? && options[:window_duration].to_i) || 2.weeks
97
+      if options[:window_duration].present? # The older option
98
+        options[:window_duration].to_i
99
+      else
100
+        (options[:window_duration_in_days] || 14).to_f.days
101
+      end
103 102
     end
104 103
 
105 104
     def std_multiple
106
-      (options[:std_multiple].present? && options[:std_multiple].to_i) || 3
105
+      (options[:std_multiple] || 3).to_f
107 106
     end
108 107
 
109 108
     def peak_spacing
110
-      (options[:peak_spacing].present? && options[:peak_spacing].to_i) || 2.days
109
+      if options[:peak_spacing].present? # The older option
110
+        options[:peak_spacing].to_i
111
+      else
112
+        (options[:min_peak_spacing_in_days] || 2).to_f.days
113
+      end
111 114
     end
112 115
 
113 116
     def group_for(event)

+ 6 - 7
spec/models/agents/peak_detector_agent_spec.rb

@@ -36,7 +36,7 @@ describe Agents::PeakDetectorAgent do
36 36
     end
37 37
 
38 38
     it "keeps a rolling window of data" do
39
-      @agent.options[:window_duration] = 5.hours
39
+      @agent.options[:window_duration_in_days] = 5/24.0
40 40
       @agent.receive build_events(:keys => [:count],
41 41
                                   :values => [1, 2, 3, 4, 5, 6, 7, 8].map {|i| [i]},
42 42
                                   :pattern => { :filter => "something" })
@@ -47,14 +47,14 @@ describe Agents::PeakDetectorAgent do
47 47
       build_events(:keys => [:count],
48 48
                    :values => [5, 6,
49 49
                                4, 5,
50
-                               8, 11,
50
+                               4, 5,
51 51
                                15, 11, # peak
52
-                               8, 5,
52
+                               8, 50, # ignored because it's too close to the first peak
53 53
                                4, 5].map {|i| [i]},
54 54
                    :pattern => { :filter => "something" }).each.with_index do |event, index|
55 55
         lambda {
56 56
           @agent.receive([event])
57
-        }.should change { @agent.events.count }.by( index == 7 ? 1 : 0 )
57
+        }.should change { @agent.events.count }.by( index == 6 ? 1 : 0 )
58 58
       end
59 59
 
60 60
       @agent.events.last.payload[:peak].should == 15.0
@@ -62,10 +62,9 @@ describe Agents::PeakDetectorAgent do
62 62
     end
63 63
 
64 64
     it "keeps a rolling window of peaks" do
65
-      @agent.options[:window_duration] = 5.hours
66
-      @agent.options[:peak_spacing] = 1.hour
65
+      @agent.options[:min_peak_spacing_in_days] = 1/24.0
67 66
       @agent.receive build_events(:keys => [:count],
68
-                                  :values => [1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 10, 1, 1, 1, 10, 1].map {|i| [i]},
67
+                                  :values => [1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 10, 1].map {|i| [i]},
69 68
                                   :pattern => { :filter => "something" })
70 69
       @agent.memory[:peaks][:something].length.should == 2
71 70
     end